﻿using System.Xml.Serialization;
using System.Xml;
using System.Collections.Generic;
using System;
using System.Reflection;
using System.Collections;
using System.Globalization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using System.IO;

namespace PersonProject
{
    public static class Serializator
    {
        public static void BinarySerialization(string path, object target)
        {
            IFormatter formatter = new BinaryFormatter();
            Stream stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
            formatter.Serialize(stream, target);
            stream.Close();
        }
        public static object BinaryDeserialization(string path)
        {
            IFormatter formatter = new BinaryFormatter();
            Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
            object obj = formatter.Deserialize(stream);
            stream.Close();
            return obj;
        }
        /// <summary>
        /// Serialization of any object
        /// </summary>
        /// <param name="path">string path and name of the file</param>
        /// <param name="p">object that must be serialized</param>
        public static void Serialization(string path, object value)
        {
            XmlSerializer serializer = new XmlSerializer(value.GetType());
            XmlWriter writer = XmlWriter.Create(path);
            serializer.Serialize(writer, value);
            writer.Close();
        }

        /// <summary>
        /// Deserialization of any object
        /// </summary>
        /// <param name="path">string path and name of the file</param>
        /// <param name="type">Type of object that you want to receive</param>
        /// <returns>return object of type provided in second argument</returns>
        public static object Deserialization(string path, Type type)
        {
            XmlSerializer serializer = new XmlSerializer(type);
            XmlReader reader = XmlReader.Create(path);
            return serializer.Deserialize(reader);
        }

        /// <summary>
        /// Serialize to XML file
        /// </summary>
        /// <param name="path">string path to filename</param>
        /// <param name="value">object to serialize</param>
        public static void ManualSerializeXml(string path, object value)
        {
            //Create Xml Document
            xmlDoc = new XmlDocument();

            //Append XML Declaration
            XmlDeclaration xmlDecl = xmlDoc.CreateXmlDeclaration("1.0", "utf-8", null);
            xmlDoc.AppendChild(xmlDecl);

            //Serialize object with recursive method and append it to XML Document
            XmlElement xmlEl = ManualXmlSerializator(value);
            xmlDoc.AppendChild(xmlEl);

            //Write Serialized XML to file
            XmlWriter writer = XmlWriter.Create(path);
            xmlDoc.WriteTo(writer);
            writer.Close();
        }

        public static object ManualDeserializeXml(string path)
        {
            object obj = null;
            xmlDoc = new XmlDocument();
            XmlReader reader = XmlReader.Create(path);
            reader.Read();
            reader.Read();
            obj = ManualXmlDeserializator(reader.ReadSubtree());
            return obj;
        }

        /// <summary>
        /// Сервисная рекурсивная функция десериализации из XML
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        private static object ManualXmlDeserializator(XmlReader reader)
        {
            object obj = null;
            while (reader.Read())
            {
                string[] nodeInfo = reader.Name.Split('.');
                Type type = null;
                if (nodeInfo[0] == "List")
                {

                    type = Type.GetType(string.Format(CultureInfo.CurrentCulture, "{0}{1}{2}", nodeInfo[1], ".", nodeInfo[2]));
                    List<dynamic> list = new List<dynamic>();

                    while (reader.Read())
                    {
                        if (reader.IsStartElement())
                        {
                            object temp = ManualXmlDeserializator(reader.ReadSubtree());
                            list.Add(temp);
                        }
                        
                    }
                    obj = list;                
                }
                
                else
                {
                    type = Type.GetType(string.Format(CultureInfo.CurrentCulture,"{0}{1}{2}", "PersonProject", ".", nodeInfo[0]), true);
                    obj = Activator.CreateInstance(type);
                    PropertyInfo[] properties = obj.GetType().GetProperties();

                    while (reader.Read())
                    {
                        nodeInfo = reader.Name.Split('.');

                        if (nodeInfo[0] == "Property")
                        {
                            reader.Read();
                            foreach (PropertyInfo prop in properties)
                            {
                                if (prop.Name == nodeInfo[1])
                                {
                                    if (prop.PropertyType == typeof(int))
                                    {
                                        prop.SetValue(obj, Convert.ToInt32(reader.Value, CultureInfo.CurrentCulture), null);
                                        reader.Read();
                                        break;
                                    }
                                    else if (prop.PropertyType == typeof(string))
                                    {
                                        prop.SetValue(obj, reader.Value, null);
                                        reader.Read();
                                        break;
                                    }
                                    else
                                    {
                                        List<dynamic> listFromXml = (List<dynamic>)ManualXmlDeserializator(reader.ReadSubtree());
                                        reader.ReadEndElement();
                                        var temp = (IList)prop.GetValue(obj, null);

                                        foreach (object o in listFromXml)
                                        {
                                            temp.Add(o);
                                        }
                                        break;
                                    }
                                }
                            }
                        }                        
                    }
                }
            }
            return obj;
        }

        /// <summary>
        /// Сервисная рекурсивная функция сериализации из XML
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private static XmlElement ManualXmlSerializator(object value)
        {
            XmlElement result = null;

            Type type = value.GetType();

            if (value is IEnumerable)
            {
                result = xmlDoc.CreateElement(string.Format(CultureInfo.InvariantCulture, "List.{0}", type.GetGenericArguments()[0]));
                foreach (object obj in value as IEnumerable)
                {
                    XmlElement temp = ManualXmlSerializator(obj);
                    result.AppendChild(temp);
                }
            }
            else
            {
                PropertyInfo[] Properies = type.GetProperties();
                result = xmlDoc.CreateElement(type.Name);
                foreach (PropertyInfo prop in Properies)
                {
                    XmlElement temp = xmlDoc.CreateElement("Property." + prop.Name);

                    if (prop.GetValue(value, null) is IEnumerable && prop.GetValue(value, null).GetType() != typeof(String))
                    {
                        XmlElement item = ManualXmlSerializator(prop.GetValue(value, null));
                        temp.AppendChild(item);
                    }
                    else
                    {
                        temp.InnerText = prop.GetValue(value, null).ToString();
                    }
                    result.AppendChild(temp);
                }
            }

            return result;
        }



        private static XmlDocument xmlDoc;
    }
}
